package com.at.window11;

import com.at.bean.WaterSensor;
import com.at.functions5.WaterSensorMapFunction3;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.datastream.WindowedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;

/**
 * 时间窗口
 *
 * @author huangchao E-mail:fengquan8866@163.com
 * @version 创建时间：2024/9/26 21:07
 */
public class TimeWindowDemo6 {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("localhost", 7777)
                .map(new WaterSensorMapFunction3());

        KeyedStream<WaterSensor, String> sensorKS = sensorDS.keyBy(WaterSensor::getId);

        // 1. 窗口分配器
        WindowedStream<WaterSensor, String, TimeWindow> sensorWS = sensorKS
                .window(TumblingProcessingTimeWindows.of(Time.seconds(10)));// 滚动窗口，窗口长度为10s
//                .window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)));// 滑动窗口，窗口长度为10s，滑动步长为5s
//                .window(ProcessingTimeSessionWindows.withGap(Time.seconds(5))); // 会话窗口，窗口长度为5s
//                .window(ProcessingTimeSessionWindows.withDynamicGap(
//                        new SessionWindowTimeGapExtractor<WaterSensor>() {
//                            @Override
//                            public long extract(WaterSensor element) {
//                                // 从数据中提取ts，作为间隔，单位 ms
//                                return element.getTs() * 1000;
//                            }
//                        }
//                )); // 会话窗口，动态间隔，每条来的数据都会更新 间隔时间
        SingleOutputStreamOperator<String> process = sensorWS
                .process(new ProcessWindowFunction<WaterSensor, String, String, TimeWindow>() {
                    private static final long serialVersionUID = 7286108888028619740L;

                    /**
                     *
                     * @param s 分组的key
                     * @param context 上下文
                     * @param elements 存的数据
                     * @param out 采集器
                     * @throws Exception
                     */
                    @Override
                    public void process(String s, ProcessWindowFunction<WaterSensor, String, String, TimeWindow>.Context context, Iterable<WaterSensor> elements, Collector<String> out) throws Exception {
                        long start = context.window().getStart();
                        long end = context.window().getEnd();
                        String windowStart = DateFormatUtils.format(start, "yyyy-MM-dd HH:mm:ss.SSS");
                        String windowEnd = DateFormatUtils.format(end, "yyyy-MM-dd HH:mm:ss.SSS");

                        long count = elements.spliterator().estimateSize();
                        out.collect("key=" + s + "的窗口【" + windowStart + "," + windowEnd + "）,数据条数：" + count + ", ==》" + elements);
                    }
                });
        process.print();

        env.execute();
    }
}
/**
 * 触发器、移除器：现成的几个窗口，都有默认的实现，一般不需要自定义
 *
 * 以 时间类型的 滚动窗口 为例，分析原理：
 * TODO 1、窗口什么时候触发 输出？
 *          时间进展 >= 窗口的最大时间戳（end - 1ms）
 *
 * TODO 2、start是怎么划分的？
 *          start= 向下取整，取窗口长度的整数倍
 *          end= start + 窗口长度
 *          窗口：左开右闭
 *
 *   long remainder = (timestamp - offset) % windowSize;
 *         // handle both positive and negative cases
 *         if (remainder < 0) {
 *             return timestamp - (remainder + windowSize);
 *         } else {
 *             return timestamp - remainder;
 *         }
 *
 * TODO 3、窗口的生命周期？
 *          创建：属于本窗口的第一条数据来的时候，new的
 *          销毁（关窗）：时间进展 >= 窗口的最大时间戳（end - 1ms） + 允许迟到的时间（默认0）
 */